{#-
 # Copyright (c) 2019 UAVCAN Development Team
 # This software is distributed under the terms of the MIT License.
 # Author: Pavel Kirienko <pavel.kirienko@zubax.com>
-#}

{%- macro serialize(t) -%}
    {{ _serialize_composite(t, 'self', 0|bit_length_set) }}
{%- endmacro -%}


{%- macro _serialize_integer(t, ref, offset) -%}
{%- if t is saturated -%}  {# Note that value ranges are internally represented as rationals. -#}
    {%- set ref = 'max(min(%s, %s), %s)'|format(ref, t.inclusive_value_range.max, t.inclusive_value_range.min) -%}
{%- endif -%}
{%- if t.standard_bit_length and offset.is_aligned_at_byte() -%}
    _ser_.add_aligned_{{ 'i' if t is SignedIntegerType else 'u' }}{{ t.bit_length }}({{ ref }})
{%- else -%}
    {%- set signedness = 'signed' if t is SignedIntegerType else 'unsigned' -%}
    _ser_.add_{{ offset|alignment_prefix }}_{{ signedness }}({{ ref }}, {{ t.bit_length }})
{%- endif -%}
{%- endmacro -%}


{%- macro _serialize_float(t, ref, offset) -%}
{%- set fun -%}
    _ser_.add_{{ offset|alignment_prefix }}_f{{ t.bit_length }}
{%- endset -%}
{#- Note that value ranges are internally represented as rationals. -#}
{%- if t is saturated -%}
    {#- We do not emit saturation code for float64 because its range matches that of the native Python's float. -#}
    {%- if t.bit_length < 64 -%}
    if _np_.isfinite({{ ref }}):
        if {{ ref }} > {{ t.inclusive_value_range.max }}.0:
            {{ fun }}({{ t.inclusive_value_range.max }}.0)
        elif {{ ref }} < {{ t.inclusive_value_range.min }}.0:
            {{ fun }}({{ t.inclusive_value_range.min }}.0)
        else:
            {{ fun }}({{ ref }})
    else:
        {{ fun }}({{ ref }})
    {%- else -%}
    # Saturation not required due to compatible native representation of "{{ t }}"
    {{ fun }}({{ ref }})
    {%- endif -%}
{%- else -%}
    {{ fun }}({{ ref }})
{%- endif -%}
{%- endmacro -%}


{%- macro _serialize_fixed_length_array(t, ref, offset) -%}
    assert len({{ ref }}) == {{ t.capacity }}, '{{ ref }}: {{ t }}'

{#- Saturation of bool[] or standard-bit-length primitive arrays is not needed because the range of native
 #- representations matches that of the final serialized value. Saturation is only needed in the case of elementwise
 #- serialization, which is implemented in the corresponding type serialization macros. -#}
{%- if t.element_type is BooleanType %}
    _ser_.add_{{ offset|alignment_prefix }}_array_of_bits({{ ref }})
{%- elif t.element_type is PrimitiveType and t.element_type.standard_bit_length %}
    _ser_.add_{{ offset|alignment_prefix -}}_array_of_standard_bit_length_primitives({{ ref }})
{%- else %}
    # Unrolled fixed-length array: {{ t }}
    {%- for index, element_offset in t.enumerate_elements_with_offsets(offset) %}
    {{ _serialize_any(t.element_type, '%s[%d]'|format(ref, index), element_offset) }}
    {%- endfor -%}
{%- endif -%}
{%- endmacro -%}


{%- macro _serialize_variable_length_array(t, ref, offset) -%}
    # Length field byte-aligned: {{ offset.is_aligned_at_byte() }}; {# -#}
     first element byte-aligned: {{ (offset + t.length_field_type.bit_length).is_aligned_at_byte() }}; {# -#}
      all elements byte-aligned: {{ (offset + t.bit_length_set).is_aligned_at_byte() }}.
    assert len({{ ref }}) <= {{ t.capacity }}, '{{ ref }}: {{ t }}'
    {{ _serialize_integer(t.length_field_type, 'len(%s)'|format(ref), offset) }}

{#- Saturation of bool[] or standard-bit-length primitive arrays is not needed because the range of native
 #- representations matches that of the final serialized value. Saturation is only needed in the case of elementwise
 #- serialization, which is implemented in the corresponding type serialization macros. -#}
{%- if t.element_type is BooleanType %}
    _ser_.add_{{ (offset + t.length_field_type.bit_length)|alignment_prefix }}_array_of_bits({{ ref }})

{%- elif t.element_type is PrimitiveType and t.element_type.standard_bit_length %}
    _ser_.add_{{ (offset + t.length_field_type.bit_length)|alignment_prefix -}}
          _array_of_standard_bit_length_primitives({{ ref }})

{%- else %}
    {%- set element_ref = 'elem'|to_template_unique_name %}
    for {{ element_ref }} in {{ ref }}:
        {{ _serialize_any(t.element_type, element_ref, offset + t.bit_length_set)|indent }}

{%- endif %}
{%- endmacro -%}


{%- macro _serialize_composite(t, ref, base_offset) -%}
    {#- The begin/end markers are emitted to facilitate automatic testing. -#}
    # BEGIN COMPOSITE SERIALIZATION: {{ t }}
{%- if t is StructureType %}
    {%- for f, offset in t.iterate_fields_with_offsets(base_offset) %}
    # BEGIN STRUCTURE FIELD SERIALIZATION: {{ f }}
    {{ _serialize_any(f.data_type, ref + '.' + (f|id), offset) }}
    # END STRUCTURE FIELD SERIALIZATION: {{ f }}
    {%- endfor -%}

{%- elif t is UnionType %}
    # Tag field byte-aligned: {{ base_offset.is_aligned_at_byte() }}; {# -#}
      values byte-aligned: {{ (base_offset + t.tag_field_type.bit_length).is_aligned_at_byte() }}
    {%- for f, offset in t.iterate_fields_with_offsets(base_offset) %}
        {%- set field_ref = ref + '.' + (f|id) %}
    # BEGIN UNION FIELD SERIALIZATION: {{ f }}
    {{ 'if' if loop.first else 'elif' }} {{ field_ref }} is not None:
        {{ _serialize_integer(t.tag_field_type, loop.index0|string, base_offset)|indent }}  # Tag {{ loop.index0 }}
        {{ _serialize_any(f.data_type, field_ref, offset)|indent }}
    # END UNION FIELD SERIALIZATION: {{ f }}
    {%- endfor %}
    else:
        raise RuntimeError('Malformed union {{ t }}')

{%- else -%}{%- assert False -%}
{%- endif %}
    # END COMPOSITE SERIALIZATION: {{ t }}
{%- endmacro -%}


{%- macro _serialize_any(t, ref, offset) -%}
    {%- if offset.is_aligned_at_byte() -%}
    assert _ser_.current_bit_length % 8 == 0, '{{ ref }}: {{ t }}'
    {% endif -%}
    {%- if t is VoidType -%}                    _ser_.skip_bits({{ t.bit_length }})
    {%- elif t is BooleanType -%}               _ser_.add_unaligned_bit({{ ref }})
    {%- elif t is IntegerType -%}               {{ _serialize_integer(t, ref, offset) }}
    {%- elif t is FloatType -%}                 {{ _serialize_float(t, ref, offset) }}
    {#- Despite the apparent similarities, fixed and variable arrays are very different when it comes to serialization,
     #- mostly because of the different logic of offset computation. -#}
    {%- elif t is FixedLengthArrayType -%}      {{ _serialize_fixed_length_array(t, ref, offset) }}
    {%- elif t is VariableLengthArrayType -%}   {{ _serialize_variable_length_array(t, ref, offset) }}
    {%- elif t is CompositeType -%}
        {%- if offset.is_aligned_at_byte() -%}
    {{ ref }}._serialize_aligned_(_ser_)  # Delegating because this object is always byte-aligned.
        {%- else -%}
    # Object {{ ref }} is not always byte-aligned, serializing in-place.
    {{ _serialize_composite(t, ref, offset) }}
        {%- endif -%}
    {%- else -%}{%- assert False -%}
    {%- endif -%}
{%- endmacro -%}
